<?php
/**
 * This file defines the static class '\BF\Type'.
 *
 * @author     Kado <ulfikado@gmail.com>
 * @category   Beluga PHP5.3+ Framework
 * @package    BF\Core
 * @since      2015-01-17 14:29
 * @version    0.1
 */

namespace BF
{

   /**
    * This class defines a objekt for getting and holding informations about a value.
    *
    * @since  v0.1
    */
   class Type
   {

      # <editor-fold defaultstate="collapsed" desc=" - - -   C O N S T A N T S   - - - - - - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * The PHP type of double values.
       */
      const PHP_DOUBLE = 'double';

      /**
       * The PHP type of float values.
       */
      const PHP_FLOAT = 'float';

      /**
       * The PHP type of integer values.
       */
      const PHP_INTEGER = 'int';

      /**
       * The PHP type of boolean values.
       */
      const PHP_BOOLEAN = 'bool';

      /**
       * The PHP type of string values.
       */
      const PHP_STRING = 'string';

      /**
       * The PHP type of NULL values.
       */
      const PHP_NULL = 'null';

      /**
       * The PHP type of resource values.
       */
      const PHP_RESOURCE = 'resource';

      /**
       * The PHP type of array values.
       */
      const PHP_ARRAY = 'array';

      /**
       * All other unknown types
       */
      const PHP_UNKNOWN = 'unknown';

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * The value.
       *
       * @var mixed
       */
      private $value;

      /**
       * Defines if the value is convertible to a string.
       *
       * @var boolean
       */
      private $isStringConvertible;

      /**
       * The string value associate with the base value.
       *
       * @var string
       */
      private $stringValue;

      /**
       * The name of the base value type.
       *
       * @var string
       */
      private $typeName;

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - >   P U B L I C   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - -">

      /**
       * Inits a new instance.
       *
       * @param  mixed $value The base value.
       * @throws \BF\Exception If $value is of type {@see \BF\Type}
       */
      public function __construct( $value )
      {
         $this->value = $value;
         $this->isStringConvertible = \BF\TypeTool::IsStringConvertible( $value, $this->stringValue );
         if ( ! $this->isStringConvertible )
         {
            $this->stringValue = null;
         }
         if ( \is_null( $value ) )
         {
            $this->typeName = \BF\Type::PHP_NULL;
         }
         elseif ( \is_string( $value ) )
         {
            $this->typeName = \BF\Type::PHP_STRING;
         }
         elseif ( \is_array( $value ) )
         {
            $this->typeName = \BF\Type::PHP_ARRAY;
         }
         elseif ( \is_object( $value ) )
         {
            if ( $value instanceof \BF\Type )
            {
               throw new \BF\Exception( 'Could not create \\BF\\Type-Instance of a \\BF\\Type-Value!' );
            }
            $this->typeName = \get_class( $value );
         }
         elseif ( \is_int( $value ) )
         {
            $this->typeName = \BF\Type::PHP_INTEGER;
         }
         elseif ( \is_double( $value ) )
         {
            $this->typeName = \BF\Type::PHP_DOUBLE;
         }
         elseif ( \is_float( $value ) )
         {
            $this->typeName = \BF\Type::PHP_FLOAT;
         }
         elseif ( \is_bool( $value ) )
         {
            $this->typeName = \BF\Type::PHP_BOOLEAN;
         }
         elseif ( \is_resource( $value ) )
         {
            $this->typeName = \BF\Type::PHP_RESOURCE;
         }
         else
         {
            $this->typeName = \BF\Type::PHP_UNKNOWN;
         }
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * Checks if the current instance is equal to defined value. If strict is FALSE only the value is checked.
       * Otherwise also the type is checked.
       *
       * @param  mixed   $value  The other value.
       * @param  boolean $strict Check strictly also the type
       * @return boolean         TRUE oder FALSE
       */
      public function equals( $value, $strict = false )
      {
         if ( ! ( $value instanceof \BF\Type ) )
         {
            $value = new \BF\Type( $value );
         }
         $res = ( $value->typeName == $this->typeName );
         if ( ! $res || ! $strict )
         {
            return $res;
         }
         if ( $value->isStringConvertible )
         {
            return ( $value->stringValue == $this->stringValue );
         }
         return ( $value->value == $this->value );
      }

      /**
       * Returns the base value.
       *
       * @return mixed
       */
      public final function getValue()
      {
         return $this->value;
      }

      /**
       * Returns the associated string value, if the base value is convertible to a string.
       * Otherwise the defined default value is returned.
       *
       * @param  string $default THis will be retuned if the base value is not string convertible (default='')
       * @return string
       */
      public final function getStringValue( $default='' )
      {
         if ( ! $this->isStringConvertible )
         {
            return $default;
         }
         return $this->stringValue;
      }

      /**
       * Returns if the base value can be converted to a string.
       *
       * @return boolean
       */
      public final function hasAssociatedString()
      {
         return $this->isStringConvertible;
      }

      /**
       * Returns the type name of the base value.
       *
       * @return string
       */
      public final function getType()
      {
         return $this->typeName;
      }

      /**
       * Returns if the base value is a PHP resource type.
       *
       * @return boolean
       */
      public final function isResource()
      {
         return $this->typeName == \BF\Type::PHP_RESOURCE;
      }

      /**
       * To string.
       *
       * @return string
       */
      public final function __toString()
      {
         return $this->stringValue;
      }

      /**
       * Returns the PHP code, defining the current base value.
       *
       * @return string
       */
      public final function getPhpCode()
      {
         $str = $this->stringValue;
         switch ( $this->typeName )
         {
            case \BF\Type::PHP_BOOLEAN:
               return ( $this->value ? 'true' : 'false' );
            case \BF\Type::PHP_DOUBLE:
            case \BF\Type::PHP_FLOAT:
            case \BF\Type::PHP_INTEGER:
               return $str;
            case \BF\Type::PHP_STRING:
               if ( \preg_match( "~[\r\n\t]+~", $str ) )
               {
                  $str = \str_replace(
                     array( '\\',  "\r", "\n", "\t", "\0", '"',  '$' ),
                     array( '\\\\','\\r','\\n','\\t','\\0','\\"','\\$' ),
                     $str
                  );
                  return '"' . $str . '"';
               }
               return "'" . \str_replace(
                     array( '\\',   "'" ),
                     array( '\\\\', "\\'" ),
                     $str
                  )
                  . "'";
            case \BF\Type::PHP_RESOURCE:
            case \BF\Type::PHP_NULL:
            case \BF\Type::PHP_UNKNOWN:
               return 'null';
            default:
               $str = \serialize( $this->value );
               if ( \preg_match( "~[\r\n\t]+~", $str ) )
               {
                  $str = \str_replace(
                     array( '\\',  "\r", "\n", "\t", "\0", '"',  '$' ),
                     array( '\\\\','\\r','\\n','\\t','\\0','\\"','\\$' ),
                     $str
                  );
               }
               else
               {
                  $str = \str_replace(
                     array('\\',   '"',   '$'),
                     array('\\\\', '\\"', '\\$'),
                     $str );
               }
               return '\unserialize("' . $str . '")';
         }
      }

      /**
       * Makes the Type instance cloneable.
       *
       * @return \BF\Type
       */
      public function __clone()
      {
         $res = null;
         switch ( $this->typeName )
         {
            case \BF\Type::PHP_BOOLEAN:
            case \BF\Type::PHP_DOUBLE:
            case \BF\Type::PHP_FLOAT:
            case \BF\Type::PHP_INTEGER:
            case \BF\Type::PHP_NULL:
            case \BF\Type::PHP_RESOURCE:
            case \BF\Type::PHP_STRING:
               $res = new \BF\Type( $this->value );
               break;
            default:
               $val = clone( $this->value );
               $res = new \BF\Type( $val );
               break;
         }
         return $res;
      }

      # </editor-fold>

   }

}

